/*
 * PrefixResolver.java July 2008
 *
 * Copyright (C) 2008, Niall Gallagher <niallg@users.sf.net>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package wx.xml.simpleframework.xml.stream;

import java.util.Iterator;
import java.util.LinkedHashMap;

/**
 * The <code>PrefixResolver</code> object will store the namespaces
 * for an element. Each namespace added to this map can be added
 * with a prefix. A prefix is added only if the associated reference
 * has not been added to a parent element. If a parent element has
 * the associated reference, then the parents prefix is the one that
 * will be returned when requested from this map.
 *
 * @author Niall Gallagher
 * @see wx.xml.simpleframework.xml.stream.OutputElement
 */
class PrefixResolver extends LinkedHashMap<String, String> implements NamespaceMap {

    /**
     * Represents the actual XML element this is associated with.
     */
    private final OutputNode source;

    /**
     * Constructor for the <code>PrefixResolver</code> object. This
     * is used to create a resolver for namespace prefixes using
     * the hierarchy of elements. Resolving the prefix in this way
     * avoids having to redeclare the same namespace with another
     * prefix in a child element if it has already been declared.
     *
     * @param source this is the XML element this is associated to
     */
    public PrefixResolver(OutputNode source) {
        this.source = source;
    }

    /**
     * This is the prefix that is associated with the source element.
     * If the source element does not contain a namespace reference
     * then this will return its parents namespace. This ensures
     * that if a namespace has been declared its child elements will
     * inherit its prefix.
     *
     * @return this returns the prefix that is currently in scope
     */
    public String getPrefix() {
        return source.getPrefix();
    }

    /**
     * This is used to add the namespace reference to the namespace
     * map. If the namespace has been added to a parent node then
     * this will not add the reference. The prefix added to the map
     * will be the default namespace, which is an empty prefix.
     *
     * @param reference this is the reference to be added
     * @return this returns the prefix that has been replaced
     */
    public String setReference(String reference) {
        return setReference(reference, "");
    }

    /**
     * This is used to add the namespace reference to the namespace
     * map. If the namespace has been added to a parent node then
     * this will not add the reference.
     *
     * @param reference this is the reference to be added
     * @param prefix    this is the prefix to be added to the reference
     * @return this returns the prefix that has been replaced
     */
    public String setReference(String reference, String prefix) {
        String parent = resolvePrefix(reference);

        if (parent != null) {
            return null;
        }
        return put(reference, prefix);
    }

    /**
     * This acquires the prefix for the specified namespace reference.
     * If the namespace reference has been set on this node with a
     * given prefix then that prefix is returned, however if it has
     * not been set this will search the parent elements to find the
     * prefix that is in scope for the specified reference.
     *
     * @param reference the reference to find a matching prefix for
     * @return this will return the prefix that is is scope
     */
    public String getPrefix(String reference) {
        int size = size();

        if (size > 0) {
            String prefix = get(reference);

            if (prefix != null) {
                return prefix;
            }
        }
        return resolvePrefix(reference);
    }

    /**
     * This acquires the namespace reference for the specified prefix.
     * If the provided prefix has been set on this node with a given
     * reference then that reference is returned, however if it has
     * not been set this will search the parent elements to find the
     * reference that is in scope for the specified reference.
     *
     * @param prefix the prefix to find a matching reference for
     * @return this will return the reference that is is scope
     */
    public String getReference(String prefix) {
        if (containsValue(prefix)) {
            for (String reference : this) {
                String value = get(reference);

                if (value != null) {
                    if (value.equals(prefix)) {
                        return reference;
                    }
                }
            }
        }
        return resolveReference(prefix);
    }

    /**
     * This method will resolve the reference or the specified prefix
     * by searching the parent nodes in order. This allows the prefix
     * that is currently in scope for the reference to be acquired.
     *
     * @param prefix the prefix to find a matching reference for
     * @return this will return the reference that is is scope
     */
    private String resolveReference(String prefix) {
        NamespaceMap parent = source.getNamespaces();

        if (parent != null) {
            return parent.getReference(prefix);
        }
        return null;
    }

    /**
     * This method will resolve the prefix or the specified reference
     * by searching the parent nodes in order. This allows the prefix
     * that is currently in scope for the reference to be acquired.
     *
     * @param reference the reference to find a matching prefix for
     * @return this will return the prefix that is is scope
     */
    private String resolvePrefix(String reference) {
        NamespaceMap parent = source.getNamespaces();

        if (parent != null) {
            String prefix = parent.getPrefix(reference);

            if (!containsValue(prefix)) {
                return prefix;
            }
        }
        return null;
    }

    /**
     * This returns an iterator for the namespace of all the nodes
     * in this <code>NamespaceMap</code>. This allows the namespaces
     * to be iterated within a for each loop in order to extract the
     * prefix values associated with the map.
     *
     * @return this returns the namespaces contained in this map
     */
    public Iterator<String> iterator() {
        return keySet().iterator();
    }
}
